package com.hd123.rumba.gwt.widget2.client.form;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.hd123.rumba.gwt.widget2.client.misc.RTextAlign;

/**
 * 表单
 * 
 * @author LiQi
 * 
 */
public class RForm extends FlexTable implements RValidatable {

  /** 样式 */
  private static final String STYLENAME = "rb-Form";
  private static final String STYLENAME_CELL = "rb-Form-cell";
  private static final String STYLENAME_CELL_GRIDLINE = "rb-Form-cell-gridline";
  private static final String STYLENAME_CELL_GRIDLINE_LASTROW = "rb-Form-cell-gridline-lastrow";
  private static final String STYLENAME_CELL_GRIDLINE_LASTCOLUMN = "rb-Form-cell-gridline-lastcolumn";

  private static final String DEPENDENT_STYLENAME_BORDER = "border";

  /** 表单列数 */
  private int columnCount = 2;

  /** 表单包含的控件列表，List< FieldConfig > */
  private List fields;

  /** 是否显示表格线 */
  private boolean showGridLine = false;

  /** 是否显示边框 */
  private boolean showBorder = false;

  /** 是否自动用RLabeledBox包装添加的field */
  private boolean autoWrapFieldInLabeledBox = true;

  /** 每个控件的标签的宽度 */
  private String labelWidth;

  /** 每个控件的标签的对齐方式 */
  private RTextAlign labelAlign;

  /** 默认的单元格纵向padding */
  private static final int DEFAULT_CELL_VERTICAL_PADDING = 0;

  /** 单元格纵向padding */
  private int cellVerticalPadding = DEFAULT_CELL_VERTICAL_PADDING;

  public RForm() {
    super();
    setStyleName(STYLENAME);
    setCellSpacing(0);
    setCellPadding(0);
    setWidth("100%");

    fields = new ArrayList();
  }

  /**
   * @param columnCount
   *          表单列数
   */
  public RForm(int columnCount) {
    this();
    setColumnCount(columnCount);
  }

  /**
   * 表单列数
   * 
   * @return
   */
  public int getColumnCount() {
    return columnCount;
  }

  /**
   * 设置表单列数，设置后需调用rebuild()才能生效。
   * 
   * @param columnCount
   */
  public void setColumnCount(int columnCount) {
    this.columnCount = columnCount;
  }

  /**
   * 是否显示表格线
   * 
   * @return
   */
  public boolean isShowGridLine() {
    return showGridLine;
  }

  /**
   * 设置是否显示表格线
   * 
   * @param showGridLine
   */
  public void setShowGridLine(boolean showGridLine) {
    if (showGridLine != this.showGridLine) {
      this.showGridLine = showGridLine;
      refreshGridLine();
    }
  }

  /**
   * 刷新表格线
   * 
   */
  protected void refreshGridLine() {
    if (showGridLine) {
      for (int row = 0; row < getRowCount(); row++)
        for (int col = 0; col < getCellCount(row); col++) {
          getCellFormatter().addStyleName(row, col, STYLENAME_CELL_GRIDLINE);

          if (row == getRowCount() - 1)
            getCellFormatter().addStyleName(row, col, STYLENAME_CELL_GRIDLINE_LASTROW);
          else
            getCellFormatter().removeStyleName(row, col, STYLENAME_CELL_GRIDLINE_LASTROW);

          if (col == getCellCount(row) - 1)
            getCellFormatter().addStyleName(row, col, STYLENAME_CELL_GRIDLINE_LASTCOLUMN);
          else
            getCellFormatter().removeStyleName(row, col, STYLENAME_CELL_GRIDLINE_LASTCOLUMN);
        }
    } else {
      for (int row = 0; row < getRowCount(); row++)
        for (int col = 0; col < getCellCount(row); col++) {
          getCellFormatter().removeStyleName(row, col, STYLENAME_CELL_GRIDLINE);
          getCellFormatter().removeStyleName(row, col, STYLENAME_CELL_GRIDLINE_LASTROW);
          getCellFormatter().removeStyleName(row, col, STYLENAME_CELL_GRIDLINE_LASTCOLUMN);
        }
    }
  }

  /**
   * 是否显示边框
   * 
   * @return
   */
  public boolean isShowBorder() {
    return showBorder;
  }

  /**
   * 设置是否显示边框
   * 
   * @param showBorder
   */
  public void setShowBorder(boolean showBorder) {
    this.showBorder = showBorder;
    if (showBorder)
      addStyleDependentName(DEPENDENT_STYLENAME_BORDER);
    else
      removeStyleDependentName(DEPENDENT_STYLENAME_BORDER);
  }

  /**
   * 是否自动用RLabeledBox包装添加的field，默认为true。为true时，
   * 如果添加的field实现了HasRFieldCaption接口， 则会在field外面包装一个RLabeledBox。
   * 
   * @return
   */
  public boolean isAutoWrapFieldInLabeledBox() {
    return autoWrapFieldInLabeledBox;
  }

  /**
   * 设置是否自动用RLabeledBox包装添加的field，默认为true。
   * 
   * @param autoWrapFieldInLabeledBox
   */
  public void setAutoWrapFieldInLabeledBox(boolean autoWrapFieldInLabeledBox) {
    this.autoWrapFieldInLabeledBox = autoWrapFieldInLabeledBox;
  }

  /**
   * 设置所有控件的标签的宽度
   * 
   * @param width
   */
  public void setLabelWidth(String width) {
    this.labelWidth = width;
    if (isBuild()) {
      for (int i = 0; i < fields.size(); i++) {
        RLabeledBox labeledBox = ((FieldConfig) fields.get(i)).getLabeledBox();
        if (labeledBox != null)
          labeledBox.setLabelWidth(width);
      }
    }
  }

  /**
   * 设置所有控件的标签的对齐方式。
   * 
   * @param labelAlign
   */
  public void setLabelAlign(RTextAlign labelAlign) {
    this.labelAlign = labelAlign;
    if (isBuild()) {
      for (int i = 0; i < fields.size(); i++) {
        RLabeledBox labeledBox = ((FieldConfig) fields.get(i)).getLabeledBox();
        if (labeledBox != null)
          labeledBox.setLabelAlign(labelAlign);
      }
    }
  }

  /**
   * 单元格纵向padding，默认为0。
   * 
   * @return
   */
  public int getCellVerticalPadding() {
    return cellVerticalPadding;
  }

  /**
   * 设置单元格纵向padding，默认为0。可用于调节行间距。如果调用本方法时表单已经构造好了，则会立即生效。
   * 
   * @param cellVerticalPadding
   */
  public void setCellVerticalPadding(int cellVerticalPadding) {
    if (this.cellVerticalPadding == cellVerticalPadding)
      return;
    this.cellVerticalPadding = cellVerticalPadding;

    if (isBuild()) {
      for (int row = 0; row < getRowCount(); row++)
        for (int col = 0; col < getCellCount(row); col++) {
          DOM.setStyleAttribute(getCellFormatter().getElement(row, col), "paddingTop",
              cellVerticalPadding + "px");
          DOM.setStyleAttribute(getCellFormatter().getElement(row, col), "paddingBottom",
              cellVerticalPadding + "px");
        }
    }
  }

  /**
   * 表单包含的控件(FieldConfig)列表
   * 
   * @return
   */
  public List getFields() {
    return fields;
  }

  /**
   * 添加一个控件，需调用rebuild()才能生效。占用一列。
   * <p>
   * 默认情况下， RForm会自动在widget外包装一个RLabeledBox
   * ，用来显示字段标题。如果不希望这样，可以调用 addField(widget,
   * false)，或者setAutoWrapFieldInLabeledBox(false)
   * 
   * @param widget
   */
  public void addField(Widget widget) {
    addField(widget, 1);
  }

  /**
   * 添加一个控件，需调用rebuild()才能生效。
   * <p>
   * 默认情况下， RForm会自动在widget外包装一个RLabeledBox
   * ，用来显示字段标题。如果不希望这样，可以调用 addField(widget, colSpan,
   * false)，或者setAutoWrapFieldInLabeledBox(false)
   * 
   * @param widget
   * @param colSpan
   *          控件占用的列宽
   */
  public void addField(Widget widget, int colSpan) {
    fields.add(new FieldConfig(widget, colSpan, null));
  }

  /**
   * 添加一个控件，需调用rebuild()才能生效。占用一列。
   * 
   * @param widget
   * @param autoWrap
   *          是否自动用RLabeledBox包装，这个设置会覆盖全局的isAutoWrapFieldInLabeledBox
   *          ()
   */
  public void addField(Widget widget, boolean autoWrap) {
    addField(widget, 1, autoWrap);
  }

  /**
   * 添加一个控件，需调用rebuild()才能生效。
   * 
   * @param widget
   * @param colSpan
   *          控件占用的列宽
   * @param autoWrap
   *          是否自动用RLabeledBox包装，这个设置会覆盖全局的isAutoWrapFieldInLabeledBox
   *          ()
   */
  public void addField(Widget widget, int colSpan, boolean autoWrap) {
    fields.add(new FieldConfig(widget, colSpan, new Boolean(autoWrap)));
  }

  /**
   * 删除一个控件，需调用rebuild()才能生效。
   * 
   * @param widget
   */
  public void removeField(Widget widget) {
    fields.remove(widget);
  }

  /**
   * 添加一个空位，需调用rebuild()才能生效。
   * 
   */
  public void addPlaceHolder() {
    addField(new Label());
  }

  /**
   * 把焦点定位于第一个控件，如果第一个控件无法获得焦点，则定位到第二个，以此类推。
   * 
   */
  public void focusOnFirstField() {
    for (int i = 0; i < fields.size(); i++) {
      final Widget widget = ((FieldConfig) fields.get(i)).getWidget();
      if (widget instanceof Focusable) {
        // 使用DeferredCommand保证若在界面尚未构造完成之前调用本方法时，焦点也能够生效。
        // 在Chrome中，还需用延时100毫秒才会真正生效，原因不明。
        DeferredCommand.addCommand(new Command() {
          public void execute() {
            Timer timer = new Timer() {
              public void run() {
                ((Focusable) widget).setFocus(true);
              }
            };
            timer.schedule(100);
          }
        });
        return;
      }
    }
  }

  public void clearValidResults() {
    for (int i = 0; i < fields.size(); i++) {
      Widget widget = ((FieldConfig) fields.get(i)).getWidget();
      if (widget instanceof RValidatable)
        ((RValidatable) widget).clearValidResults();
    }
  }

  public boolean isValid() {
    for (int i = 0; i < fields.size(); i++) {
      Widget widget = ((FieldConfig) fields.get(i)).getWidget();
      if (widget.isVisible() && widget instanceof RValidatable
          && !((RValidatable) widget).isValid())
        return false;
    }
    return true;
  }

  public List getInvalidMessages() {
    List list = new ArrayList();
    for (int i = 0; i < fields.size(); i++) {
      Widget widget = ((FieldConfig) fields.get(i)).getWidget();
      if (widget.isVisible() && widget instanceof RValidatable)
        list.addAll(((RValidatable) widget).getInvalidMessages());
    }
    return list;
  }

  public boolean validate() {
    if (!isVisible())
      return true;

    boolean valid = true;
    for (int i = 0; i < fields.size(); i++) {
      Widget widget = ((FieldConfig) fields.get(i)).getWidget();
      if (widget.isVisible() && widget instanceof RValidatable)
        valid &= ((RValidatable) widget).validate();
    }
    return valid;
  }

  /**
   * 设置所有控件的必填属性
   * 
   * @param required
   */
  public void setFieldRequired(boolean required) {
    for (int i = 0; i < fields.size(); i++) {
      Widget widget = ((FieldConfig) fields.get(i)).getWidget();
      if (widget instanceof WithRequired)
        ((WithRequired) widget).setRequired(required);
    }
  }

  /**
   * 是否已经构造过
   * 
   * @return
   */
  public boolean isBuild() {
    return getRowCount() > 0;
  }

  /**
   * 根据fields内容重新构造表单，隐藏的field不占用位置。
   * 
   */
  public void rebuild() {
    // 清空表单
    for (int row = getRowCount() - 1; row >= 0; row--)
      removeRow(row);

    // 使各列等宽
    for (int i = 0; i < columnCount; i++)
      getColumnFormatter().setWidth(i, 100 / columnCount + "%");

    // 逐个添加控件
    int row = 0, col = 0, formCol = 0;
    for (int i = 0; i < fields.size(); i++) {
      FieldConfig field = (FieldConfig) fields.get(i);

      Widget widget = field.getWidget();
      if (!widget.isVisible())
        continue;

      // 包装
      Widget cellWidget = null; // 真正被添加进表格的控件
      boolean wrap = field.getAutoWrap() == null ? autoWrapFieldInLabeledBox : field.getAutoWrap()
          .booleanValue();
      if (wrap && widget instanceof HasRFieldCaption && !(widget instanceof RLabeledBox)) {
        RLabeledBox wrapper = new RLabeledBox((HasRFieldCaption) widget);
        field.setWrapper(wrapper);
        cellWidget = wrapper;
      } else {
        cellWidget = widget;
      }

      // 标签宽度和对齐
      RLabeledBox labeledBox = field.getLabeledBox();
      if (labeledBox != null) {
        if (labelWidth != null)
          labeledBox.setLabelWidth(labelWidth);
        if (labelAlign != null)
          labeledBox.setLabelAlign(labelAlign);
      }

      // 若当前行剩余列不足跨列则需另起一行
      if (formCol > 0 && formCol + field.getColSpan() - 1 >= columnCount) {
        setWidget(row, col, new Label(" "));
        getFlexCellFormatter().setColSpan(row, col, columnCount - formCol);
        getCellFormatter().setStyleName(row, col, STYLENAME_CELL);
        row++;
        col = 0;
        formCol = 0;
      }

      // 添加到表格
      setWidget(row, col, cellWidget);

      // 设置跨列
      getFlexCellFormatter().setColSpan(row, col, field.getColSpan());

      // 设置单元格样式
      getCellFormatter().setStyleName(row, col, STYLENAME_CELL);

      // 设置单元格纵向padding
      if (cellVerticalPadding != DEFAULT_CELL_VERTICAL_PADDING) {
        DOM.setStyleAttribute(getCellFormatter().getElement(row, col), "paddingTop",
            cellVerticalPadding + "px");
        DOM.setStyleAttribute(getCellFormatter().getElement(row, col), "paddingBottom",
            cellVerticalPadding + "px");
      }

      col++;
      formCol += field.getColSpan();
      if (formCol >= columnCount && i < fields.size() - 1) {
        row++;
        col = 0;
        formCol = 0;
      }
    }

    // 在表单尾部创建空单元格，以增强带表格线时的美观度
    if (formCol <= columnCount - 1) {
      setWidget(row, col, new Label(" "));
      getFlexCellFormatter().setColSpan(row, col, columnCount - formCol);
      getCellFormatter().setStyleName(row, col, STYLENAME_CELL);
    }

    // 刷新表格线
    refreshGridLine();
  }

  /**
   * 表单域配置
   * 
   * @author LiQi
   * 
   */
  public class FieldConfig {

    /** 控件 */
    private Widget widget;

    /** 控件占用的列数 */
    private int colSpan = 1;

    /**
     * 是否自动用RLabeledBox包装，
     * 值为null表示遵从整体设置isAutoWrapFieldInLabeledBox()
     */
    private Boolean autoWrap;

    /** 控件外包装的RLabeledBox */
    private RLabeledBox wrapper;

    public FieldConfig(Widget widget, int colSpan, Boolean autoWrap) {
      this.widget = widget;
      this.colSpan = colSpan;
      this.autoWrap = autoWrap;
    }

    /**
     * 如果控件被自动包装过，则返回自动包装的那个RLabeledBox，如果控件本身就是一个RLabeledBox，
     * 则返回控件本身，否则返回null
     * 
     * @return
     */
    public RLabeledBox getLabeledBox() {
      if (wrapper != null)
        return wrapper;
      else if (widget instanceof RLabeledBox)
        return (RLabeledBox) widget;
      else
        return null;
    }

    public Widget getWidget() {
      return widget;
    }

    public void setWidget(Widget widget) {
      this.widget = widget;
    }

    public int getColSpan() {
      return colSpan;
    }

    public void setColSpan(int colSpan) {
      this.colSpan = colSpan;
    }

    public Boolean getAutoWrap() {
      return autoWrap;
    }

    public void setAutoWrap(Boolean autoWrap) {
      this.autoWrap = autoWrap;
    }

    public RLabeledBox getWrapper() {
      return wrapper;
    }

    public void setWrapper(RLabeledBox wrapper) {
      this.wrapper = wrapper;
    }
  }

}
